package src.mcpichess.control;

import java.util.ArrayList;

import src.mcpichess.uci.Board;
import src.mcpichess.uci.Engine;
import src.mcpichess.uci.Evaluation2;
import src.mcpichess.uci.Global;
import src.mcpichess.uci.HistoryWriter;
import src.mcpichess.uci.Move;
import src.mcpichess.uci.MoveFunctions;
import src.mcpichess.uci.SEE;

// TO DO
// Make a move to check if a king is in check 
//Method called called when game is finished

/** UCI controls the interaction between MCPI code and MagnumChess code **/
public class UCI 
{
	
	/** time management variables */
    public static final int DEFAULT_WTIME = 1000;
    public static final int DEFAULT_BTIME = 1000;
    public static final int DEFAULT_WINC = 0;
    public static final int DEFAULT_BINC = 0;
    public static final int DEFAULT_TOGO = 35;
	
	/** Public static variables **/
	public Engine theSearch;
    public Board board;
    public HistoryWriter writer;
    public Evaluation2 eval;
    public MoveFunctions moveFunctions;
    public SEE see;
    public static final String[] PieceArray = {"P","R","N","B","Q","K"};
    
    /** Public non-static variables **/
    int movetime;
	int maxMoveTime;
	int searchDepth;
	int wtime=0;
	int btime=0;
	int winc=0;
	int binc=0;
	int togo = 0;
	boolean infinite = false;
	boolean gameOver = false;
	
	/* Used for FEN construction **/
	boolean isWhite = true;
	int halfMoves = 0;
	int numMoves = 0;
	
	private ChessEventListener onChessEventListener = null;
	
	/* List of every move made */
	/* Used to send the position string */
	private ArrayList<String> moveHistory = new ArrayList<String>();
    
	
	/** Public constructor: Provide the class with neccessary objects **/
	public UCI()
	{
		/* Initialize objects */
		see = new SEE();
        eval = new Evaluation2();
        moveFunctions = new MoveFunctions();
        board = Board.getInstance();
        writer = new HistoryWriter();
        board.newGame();
        theSearch = new Engine();
        
        sendCommand("uci");
	}
	
	public Move sendAndReceiveMove(Move move, String[][] board)
	{
		String fen = boardToFen(board, true);
		String moves = move.moveString;
		moveHistory.add(moves);
		
		String total = "position fen " + fen + " moves " + moves;
		//String total = "position startpos moves " + commaDelimit(moveHistory);
		sendCommand(total);
		
		String ret = sendCommand("go");
		Move retMove = new Move(ret);
		
		/* Add moves to history */
		moveHistory.add(ret);
		
		return retMove;
	}
   
  /** Start a new game **/ 
  public void startNewGame()
  {
    sendCommand("ucinewgame");
  }
	
	
	/** Return whether the human is in check **/
	public boolean isInCheck()
	{
		return theSearch.inCheck(0);
	}
	
	
	/** Return whether the human is in checkmate **/
	public boolean isCheckmate()
	{
		return theSearch.isCheckmate;
	}
	
	
	
	/** Converts an entire board-array (defined by DSchon) to a FEN String.
	 0,0 in the board array refers to 8,1 on the actual chess board **/
	public String boardToFen(String[][] boardArray, boolean white)
	{
		String fen = "";
		
		/* We need to loop through the rows and build the board from White's point of view, starting with rank/row 8 */
		for(int i = 7; i >= 0; i--)
		{
			fen += "/" + rowToFen(boardArray[i]);
		}
		
		fen = fen.substring(1); //Remove the first char (a '/' in this case )
		
		/* Tack on all extra FEN data, unused at this time */
		if(white)
			fen += " w";
		else
			fen += " b";
		
		fen += " KQkq - 0 ";
		
		int noMoves = 1;
		if(board.moveCount != 0)
			noMoves = board.moveCount;
		
		fen += String.valueOf(noMoves);
		return fen.trim();
	}
	
	/** Convert a String[], which represents a row (Defined by DSchon), to a partial FEN String. 
	    According to DSchon's layout, index 0 of a row refers to H, and index 7 refers to A **/
	public static String rowToFen(String[] row)
	{
		String fen = "";
		int count = 0;
		
		/* Loop backwards */
		for(int i = 7; i >= 0; i--)
		{
			String data = row[i];
			
			if(data.equals("00")) //It's an empty square!
			{
				count++;
			}
			else //It's got a piece code!
			{
				if(count != 0) //Check to see if we've been counting blank squares
				{
					fen += String.valueOf(count);
				}
				
				fen += getPieceCode(data); //Append the FEN piece code
				
				count = 0;
			}
		}
		
		/* Check to see if we've got any un-printed counts */
		if(count != 0)
		{
			fen += String.valueOf(count);
		}
		
		return fen;
	}
	
	
	/** Given DSchon's piece code, return a FEN-compatible piece code **/
	public static String getPieceCode(String piece)
	{
		if(piece.length() != 2) //Make sure the string only has 2 chars
		{
			return "ERROR";
		}
		
		char player = piece.charAt(0);
		int code = Integer.parseInt(String.valueOf(piece.charAt(1)));
		
		String newCode = PieceArray[code-1];
		
		if(player == '2')
			newCode = newCode.toLowerCase();
		
		return newCode;
	}
	
	/** Decide whether a given move will put the current player into check **/
	public boolean willEnterCheck(String[][] board, Move m)
	{
		//Stuff
		return false;
	}
	
	/** This low-level method sends UCI commands to the board **/
	public String sendCommand(String cmd)
	{
		String retString = "";
		
		/* This is the command-sorting code, copied straight from MagnumChess */
		if(cmd.startsWith("quit"))
            System.exit(0);
		
		else if(cmd.equals("eval_dump")) 
		{
	        Evaluation2.getEval(1);
	        Evaluation2.printEvalTerms();
	        Evaluation2.getEval(-1);
	        Evaluation2.printEvalTerms();
		}
		
		else if ("isready".equals( cmd ))
			System.out.println("readyok");

        if(cmd.startsWith("perft")) 
        {
            cmd = cmd.substring(5);
            cmd = cmd.trim();
            int depth = Integer.parseInt(cmd.substring(0));
            theSearch.PerftTest(depth);
        }
        
        if(cmd.startsWith("divide")) 
        {
            cmd = cmd.substring(6);
            cmd = cmd.trim();
            int depth = Integer.parseInt(cmd.substring(0));
            theSearch.Divide(depth);
        }
        
        if(cmd.startsWith("position")) 
        {
			if(cmd.indexOf(("startpos"))!= -1) 
			{
				int mstart = cmd.indexOf("moves");
				if(mstart>-1) 
				{
					String moves = cmd.substring(mstart+5);
					board.undoAll();

					HistoryWriter.acceptMoves(moves);
				}
			} 
			else 
			{			//reading in a fen string
				int mstart = cmd.indexOf("moves");
				if(mstart> -1) 
				{
					board.undoAll();
                    String fen = cmd.substring(cmd.indexOf("fen")+4,mstart-1);
					board.acceptFen(fen);
                    String moves = cmd.substring(mstart+5);
					HistoryWriter.acceptMoves(moves);
				} 
				else 
				{
					String fen = cmd.substring(cmd.indexOf("fen")+4);
					board.acceptFen(fen);
				}
			}
		}
        
        else if(cmd.startsWith("setoption")) 
        {
			int index = cmd.indexOf("Hash");
			if(index != -1)  
			{
				index = cmd.indexOf("value");
				cmd = cmd.substring(index+5);
				cmd = cmd.trim();
				int hashSize = Integer.parseInt(cmd.substring(0));
				Global.HASHSIZE = hashSize*32768;
				Engine.resetHash();
				System.out.println("info string hashsize is "+hashSize);
			} 
			else if(cmd.indexOf("Evaluation Table")!= -1) 
			{
                index = cmd.indexOf("value");
                cmd = cmd.substring(index+5);
				cmd = cmd.trim();
				int evalSize = Integer.parseInt(cmd.substring(0));
                Global.EvalHASHSIZE = evalSize * 87381;
                Evaluation2.reSizeEvalHash();
                System.out.println("info string evalHash is "+evalSize);
            } 
			else if(cmd.indexOf("Pawn Table") != -1) 
			{
                index = cmd.indexOf("value");
                cmd = cmd.substring(index+5);
				cmd = cmd.trim();
				int evalSize = Integer.parseInt(cmd.substring(0));
                Global.EvalHASHSIZE = evalSize * 43690;
                Evaluation2.reSizeEvalHash();
                System.out.println("info string pawnHash is "+evalSize);
            }  
			else 
			{
                System.out.println("info string command not recognized");
            }
		}
		
        else if(cmd.startsWith("go")) 
        {
			movetime = 0;
			maxMoveTime = 0;
			searchDepth = 0;
			if(cmd.indexOf("depth")!=-1) 
			{
				try
				{
					int index = cmd.indexOf("depth");
					cmd = cmd.substring(index+5);
					cmd = cmd.trim();
					searchDepth = Integer.parseInt(cmd.substring(0));
					movetime = 9999999;
					maxMoveTime = movetime;
				}
				catch(NumberFormatException ex) {}
			}
			else if(cmd.indexOf("movetime")!=-1) 
			{
				try
				{
					int index = cmd.indexOf("movetime");
					cmd = cmd.substring(index+8);
					cmd = cmd.trim();
					movetime = Integer.parseInt(cmd.substring(0));
					maxMoveTime = movetime;
					searchDepth = 40;
				}
				catch(NumberFormatException ex) {}
			}
			else if(cmd.indexOf("infinite")!=-1) 
			{
				infinite = true;	
				searchDepth = 40;
				movetime = 1000;
				maxMoveTime = movetime;
			}	
			else 
			{				//extract the clock times and increments
				try 
				{
					searchDepth = 40;
					
                    String temp;
					int index = cmd.indexOf("wtime");

                    if (index == -1)
                        wtime = DEFAULT_WTIME;
                    else 
                    {
                        temp = cmd.substring(index+5).trim();
                        wtime = Integer.parseInt(temp.substring(0,temp.indexOf(" ")));
                    }
                    
					index = cmd.indexOf("btime");
                    if (index == -1)
                        btime = DEFAULT_BTIME;
                    else 
                    {
                        temp = cmd.substring(index+5).trim();
                        if(temp.indexOf(" ")!=-1)
                            btime = Integer.parseInt(temp.substring(0,temp.indexOf(" ")));
                        else
                            btime = Integer.parseInt(temp);
                    }
                    
					index = cmd.indexOf("winc");
                    if (index == -1)
                        winc = DEFAULT_WINC;
                    else 
                    {
                        temp = cmd.substring(index+4).trim();      
                        if(temp.indexOf(" ")!=-1)
                            winc = Integer.parseInt(temp.substring(0,temp.indexOf(" ")));
                        else 
                            winc = Integer.parseInt(temp);
                    }
                    
					index = cmd.indexOf("binc");
                    if (index == -1)
                        binc = DEFAULT_BINC;
                    else 
                    {
                        temp = cmd.substring(index+4);
                        temp = temp.trim();
                        if(temp.indexOf(" ")!=-1)
							binc = Integer.parseInt(temp.substring(0,temp.indexOf(" ")));
						else
							binc = Integer.parseInt(temp);
                    }
                    
                    index = cmd.indexOf("movestogo");
                    if(index == -1) 
                        togo = DEFAULT_TOGO;
                    else 
                    {
                        temp = cmd.substring(index+9).trim();
                        togo = Integer.parseInt(temp);
                    }
                    
					if(board.getTurn()==1)	
					{
						//black moving
						movetime = Math.max(0,(btime/togo + binc)-200);
		                //reduce the move time a little, as most of the time we will be extending this time to find the first move of the last iteration
		                movetime = (int)(((double)movetime)* 0.85);
		                int  maxTimeLimit = (int)(((double)btime + (double)binc)*0.60);
		                maxMoveTime = Math.min(movetime * 4, maxTimeLimit);
					}
					else 
					{
						movetime = Math.max(0,(wtime/togo + winc)-200);
						//reduce the move time a little, as most of the time we will be extending this time to find the first move of the last iteration
						movetime = (int)(((double)movetime)* 0.85);
						int  maxTimeLimit = (int)(((double)wtime + (double)winc)*0.60);
						maxMoveTime = Math.min(movetime * 4, maxTimeLimit);
					}
					
              // on the last move before the time is increased, the move time will be higher than the maxMoveTime,
              // so we adjust the maxMoveTime to be equal to the movetime
              if(movetime > maxMoveTime)  
              {
                 maxMoveTime = movetime;
              }
           }
				catch(NumberFormatException ex) 
				{
					ex.printStackTrace(System.err);
				}
			}
			
			String move = theSearch.search(movetime, maxMoveTime, searchDepth,infinite);
			System.out.println("bestmove "+move);
			retString = move;
		}
        
	    else if(cmd.equals("ucinewgame")) 
	    {
			board.newGame();
			Engine.resetHash();	
		}
        
        return retString;
	}


	/** Copy one String[][] to another **/
	public static String[][] copyBoard(String[][] original)
	{
		String[][] newArray = new String[original.length][original[0].length];
		
		for(int x = 0; x < original.length; x++)
		{
			for(int y = 0; y < original[0].length; y++)
			{
				newArray[x][y] = original[x][y];
			}
		}
		
		return newArray;
	}
	
	/** Return a CSV String of all elements in the given array **/
	public static String commaDelimit(ArrayList<String> data)
	{
		if(data.size() == 0)
			return "ERROR";
		
		String total = "";
		
		for(String s : data)
			total += s.trim() + ",";
		
		/* Trim off the last comma */
		total = total.substring(0, total.length() - 1);
		return total;
			
	}
	
	/** Set our instance of the listener **/
	public void setOnChessEventListener(ChessEventListener l)
	{
		this.onChessEventListener = l;
	}
	
	/** Interface used for communicating major chess events **/
	public interface ChessEventListener
	{
		public void onCheck();
		public void onGameOver();
	}

}
